package com.sds.system.daos;

import com.google.common.collect.Sets;
import com.sds.system.models.IdentifiedObject;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

/**
 * Copyright 2011 Tripwire, Inc. All Rights Reserved.
 * ver 1.0 9/16/11 DungTV8
 */
public class AbstractDAO {
    private Logger logger = Logger.getLogger(getClass().getName());

    private final EntityManagerFactory emf;

    public AbstractDAO(EntityManagerFactory emf) {
        this.emf = emf;
        logger.info("Starting the entity management service...");
    }

    public <T extends IdentifiedObject> ObjectManagerQuery<T> getObjectManager(Class<T> objectClass) {
        return new ObjectManager<T>(objectClass);
    }

    public class ObjectManager<T extends IdentifiedObject> implements ObjectManagerQuery<T>, ObjectManagerPerformer<T>, ObjectManagerResult<T> {

        private Class<T> objectClass;
        private EntityManager entityManager;
        private T singleResult = null;
        private Collection<T> collectionResult = null;
        private QueryResult queryResult = QueryResult.UNDEFINED;

        private ObjectManager(Class<T> objectClass) {
            this.objectClass = objectClass;
            entityManager = emf.createEntityManager();
        }

        /**
         * Store or update this object to DB with this primary key
         *
         * @param object the object to store
         */
        public ObjectManagerPerformer<T> store(T object) {
            assertNotNull(object.getId(), "The id field should not be empty for " + objectClass.getName());
            assertEqualsNullSafe(queryResult, QueryResult.UNDEFINED, "You have already query the database for this session.");
            T oldObject = entityManager.find(objectClass, object.getId());
            logger.info("Persisting " + object);
            if (oldObject != null) {
                logger.info("...found an old object with the same id");
                entityManager.remove(oldObject);
            }
            entityManager.persist(object);
            singleResult = object;
            queryResult = QueryResult.SINGLE;
            return this;
        }


        /**
         * Find a object with the specified id
         *
         * @param id the id
         */
        public ObjectManagerPerformer<T> findById(String id) {
            assertNotNull(id, "The id field should not be empty for " + objectClass.getName());
            assertEqualsNullSafe(queryResult, QueryResult.UNDEFINED, "You have already query the database for this session.");
            singleResult = entityManager.find(objectClass, id);
            queryResult = QueryResult.SINGLE;
            return this;
        }

        /**
         * List all objects
         */
        public ObjectManagerPerformer<T> findAll() {
            assertEqualsNullSafe(queryResult, QueryResult.UNDEFINED, "You have already query the database for this session.");
            collectionResult = Sets.<T>newHashSet(entityManager.createQuery("select from " + objectClass.getName()).getResultList());
            queryResult = QueryResult.COLLECTION;
            return this;
        }

        public ObjectManagerPerformer<T> deleteAll() {
            assertEqualsNullSafe(queryResult, QueryResult.UNDEFINED, "You have already query the database for this session.");
            entityManager.createQuery("delete from " + objectClass.getName()).executeUpdate();
            return this;
        }

        public ObjectManagerResult<T> perform() {
            entityManager.close();
            return this;
        }

        public T getSingleResult() {
            assertEqualsNullSafe(queryResult, QueryResult.SINGLE, "No sing result in this chain cast");
            return singleResult;
        }

        public Collection<T> getCollectionResult() {
            assertEqualsNullSafe(queryResult, QueryResult.COLLECTION, "No collection result in this chain cast");
            return collectionResult;
        }

        private void assertNotNull(Object object, String message) {
            if (object == null) {
                throw new IllegalArgumentException(message);
            }
        }

        private void assertEqualsNullSafe(Object object, Object value, String message) {
            assertNotNull(object, message);
            assertNotNull(value, message);
            if (!object.equals(value)) {
                throw new IllegalArgumentException(message);
            }
        }
    }

    public interface ObjectManagerQuery<T extends IdentifiedObject> {
        ObjectManagerPerformer<T> store(T object);

        ObjectManagerPerformer<T> findById(String id);

        ObjectManagerPerformer<T> findAll();

        ObjectManagerPerformer<T> deleteAll();
    }

    public interface ObjectManagerPerformer<T extends IdentifiedObject> {
        ObjectManagerResult<T> perform();
    }

    public interface ObjectManagerResult<T extends IdentifiedObject> {
        T getSingleResult();

        Collection<T> getCollectionResult();
    }

    private enum QueryResult {
        UNDEFINED,
        SINGLE,
        COLLECTION
    }
}
